﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Runtime.Remoting.Messaging;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Security.Permissions;

namespace ListItTaxi.Web
{
    public class iPhoneAPNS
    {
        const string ApsHost = "gateway.sandbox.push.apple.com";
        const Int32 ApsPort = 2195;

        SslStream _sslStream;
        System.Net.Sockets.TcpClient _tcpClient;
        X509Certificate _certificate;

        public iPhoneAPNS(X509Certificate certificate)
        {
            _certificate = certificate;
            CreateStream();
        }

        public iPhoneAPNS(string certName)
        {
            _certificate = FindCertificate(certName);
            CreateStream();
        }

        bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            return true; // todo: check for server cert errors here..
        }

        X509Certificate SelectLocalCertificate(object sender, string targetHost, X509CertificateCollection localCertificates,
            X509Certificate remoteCertificate, string[] acceptableIssuers)
        {
            return _certificate;
        }

        public void CreateStream()
        {
            System.Diagnostics.Debug.Assert(_certificate != null);
            _tcpClient = new System.Net.Sockets.TcpClient(ApsHost, ApsPort);
            _sslStream = new SslStream(_tcpClient.GetStream(), true,
                new RemoteCertificateValidationCallback(ValidateServerCertificate),
                new LocalCertificateSelectionCallback(SelectLocalCertificate));

            X509CertificateCollection clientCertificateCollection = new X509CertificateCollection(new X509Certificate[1] { _certificate });
            _sslStream.AuthenticateAsClient(ApsHost, clientCertificateCollection, System.Security.Authentication.SslProtocols.Default, false);
        }

        public X509Certificate2 FindCertificate(string certName)
        {
            // We assume the certificate was exported from the Mac keychain as .p12 and 
            // imported into "Personal" user store by double-clicking the file in Windows.
            X509Store store = new X509Store(StoreLocation.CurrentUser);
            store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
            foreach (X509Certificate2 c in store.Certificates)
                if (c.SubjectName.Name.Contains(certName))
                    return c;
            return null;
        }

        const byte PushCommand = 0;

        byte[] LengthBytes(Int32 value)
        {
            return new byte[] { (byte)(value >> 8), (byte)value };
        }

        void PushRawNotification(byte[] deviceToken, string json)
        {
            using (MemoryStream m = new MemoryStream())
            {
                using (BinaryWriter w = new BinaryWriter(m))
                {
                    w.Write(PushCommand);

                    w.Write(LengthBytes(deviceToken.Length));
                    w.Write(deviceToken);

                    byte[] data = Encoding.UTF8.GetBytes(json);
                    w.Write(LengthBytes(data.Length));
                    w.Write(data);

                    w.Flush();

                    //todo: handle connetion loss & reconnecting
                    _sslStream.Write(m.ToArray());
                    _sslStream.Flush();
                }
            }
        }

        public void PushMessageNotification(byte[] deviceToken, string message)
        {
            //todo: escape illegal JSON chars in message
            string json = string.Format("{{\"aps\":{{\"alert\":\"{0}\"}}}}", message);
            PushRawNotification(deviceToken, json);
        }

        public void PushBadgeNotification(byte[] deviceToken, Int32 badge)
        {
            string json = string.Format("{{\"aps\":{{\"badge\":{0}}}}}", badge);
            PushRawNotification(deviceToken, json);
        }

        public void PushAudioNotification(byte[] deviceToken, string sound)
        {
            //todo: escape illegal JSON chars in sound name
            string json = string.Format("{{\"aps\":{{\"sound\":\"{0}\"}}}}", sound);
            PushRawNotification(deviceToken, json);
        }

        public void PushCombinedNotification(byte[] deviceToken, string message, Int32? badge, string sound)
        {
            string data = string.Empty;
            if (!string.IsNullOrEmpty(message))
            {
                //todo: escape illegal JSON chars in message
                data += string.Format("\"alert\":\"{0}\"", message);
            }
            if (badge.HasValue)
            {
                if (data.Length > 0)
                    data += ",";
                data += string.Format("\"badge\":\"{0}\"", badge.Value);
            }

            if (!string.IsNullOrEmpty(sound))
            {
                if (data.Length > 0)
                    data += ",";
                //todo: escape illegal JSON chars in message
                data += string.Format("\"sound\":\"{0}\"", sound);
            }

            string json = string.Format("{{\"aps\":{{{0}}}}}", data);
            PushRawNotification(deviceToken, json);
        }
     }
}

